<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <script type="text/javascript" src="protovis-d3.2.js"></script>
    <script type="text/javascript" src="file://C:/Documents and Settings/dolsson/Desktop/My Dropbox/edu/visualization/javascript/data/northampton item 2010.js"></script> 
    <style type="text/css">
      body { color: black; background-color: white }
      #fig { width: 1000px; height: 700px }
    </style>
  </head>
    <body>
        <div id="fig">
        <script type="text/javascript+protovis">

        var itemData = pv.dom(itemData0).root("itemData");
        var level = {'root': 0, 'year': 1, 'month': 2, 'school': 3, 'grade': 4,
                    'subject': 5, 'strand': 6, 'standard': 7, 'type': 8,
                    'item': 9, 'scoreData': 10, 'score': 11};
        function subjectNode(root, subject, grade, school, year, month) {
            // Month optional. If omitted we'll assume JUNE, the month for all grade 3-8 tests.
            if (!month) month = 'JUNE';
            var subjectNode, gradeNode, schoolNode, yearNode, monthNode;
            if ( (yearNode = root.childNodes.filter(function(n) n.nodeName == year))
            && (monthNode = yearNode[0].childNodes.filter(function(n) n.nodeName == month))
            && (schoolNode = monthNode[0].childNodes.filter(function(n) n.nodeName == school))
            && (gradeNode = schoolNode[0].childNodes.filter(function(n) n.nodeName == grade))
            && (subjectNode = gradeNode[0].childNodes.filter(function(n) n.nodeName == subject))
            )
                return subjectNode[0];
            else
                return null;
        }

        {
            // Accumulate scores up the tree, to the subject level.
            function accumulateStudent(score) {
                // parental links: score > scores > score level > target ancestor
                var ancestor = score.parentNode.parentNode.parentNode;
                var scores;
                // Does ancestor already have a scores node?
                if (ancestor.lastChild.nodeName == 'scores') {
                    scores = ancestor.lastChild;
                    // Is this student already registered with the ancestor?
                    var isRegistered;
                    for (var i in scores.childNodes) {
                        if (isRegistered = (scores.childNodes[i].nodeName == score.nodeName)) {
                            // Found 'em. Add the current score.
                            scores.childNodes[i].nodeValue += score.nodeValue;
                            break;
                        }
                    }
                    if (!isRegistered) {
                        // Register this student: create a new score node.
                        var newScore = new pv.Dom.Node(score.nodeValue);
                        newScore.nodeName = score.nodeName;
                        scores.appendChild(newScore);
                    }
                } else {
                    // Create a scores node and a new student score node under it.
                    var newScores = new pv.Dom.Node;
                    newScores.nodeName = 'scores';
                    var newScore = new pv.Dom.Node(score.nodeValue);
                    newScore.nodeName = score.nodeName;
                    newScores.appendChild(newScore);
                    ancestor.appendChild(newScores);
                }
            }
            function accumulatePossible(score) {
                var ancestor = score.parentNode.parentNode;
                // Does ancestor already have a possible-score node?
                if (ancestor.firstChild.nodeName == 'possible') {
                    // Add this score to the existing score.
                    ancestor.firstChild.nodeValue += score.nodeValue;
                } else {
                    // Create a new possible node.
                    var newPossible = new pv.Dom.Node(score.nodeValue);
                    newPossible.nodeName = 'possible';
                    ancestor.insertBefore(newPossible, ancestor.firstChild);
                }
            }
            function totals(n, l) {
                if (n.parentNode && n.parentNode.nodeName == 'scores' && l > level.subject + 2)
                    accumulateStudent(n);
                if (n.firstChild && n.firstChild.nodeName == 'possible' && l > level.subject) {
                    accumulatePossible(n.firstChild);
                }
            }
            itemData.visitAfter(totals);
        }
        function isLevelNode0(node) {
            return node.lastChild && node.lastChild.nodeName == 'scores';
        }
        function levelCount(node) {
            if (isLevelNode(node)) {
                return node.lastChild.childNodes.length;
            } else {
                return null;
            }
        }
        function levelAverage0(node) {
            if (isLevelNode(node)) {
                // Assume the scores node has children.
                var scores = node.lastChild.childNodes;
                var sum = 0;
                for (var i in scores) sum += scores[i].nodeValue;
                return sum / scores.length;
            } else {
                return null;
            }
        }
        function levelPossible0(node) {
            if (node.firstChild && node.firstChild.nodeName == 'possible') {
                return node.firstChild.nodeValue;
            } else {
                return null;
            }
        }
        if (0)
        {
            var indent = 0;
            var level = -1;
            function display(n, l) {
                var styleType = ['decimal', 'lower-alpha', 'circle', 'none', 'none', 'none', 'none', 'none'];
                if (isLevelNode(n)) {
                    if (level == -1) level = l;
                    for (; l > level; level++, indent++) document.write('<ol style="list-style-type:' + styleType[indent] + '">');
                    for (; l < level; level--, indent--) document.write('</ol>');
                    document.write('<li>' + ' ' + n.nodeName + ': <b>' + levelCount(n) + '</b> scores, average <b>' + levelAverage(n).toFixed(1) + '</b> out of <b>' + levelPossible(n) + "</b></li>\n");
                }
            }
            document.write('<ol style="list-style-type:upper-roman">');
            itemData.visitBefore(display);
            document.write('</ol>');
            for (; indent > 0; indent--) document.write('</ol>');
        }
        function stowScores(node) {
            if (!node.nodeValue) node.nodeValue = new Object();
            if (node.firstChild.nodeName == 'possible') {
                node.nodeValue.possible = node.removeChild(node.firstChild);
            }
            if (node.lastChild.nodeName == 'scores') {
                node.nodeValue.scores = node.removeChild(node.lastChild);
            }
        }
        function isLevelNode(node) {
            return node.nodeValue && node.nodeValue.scores && node.nodeValue.scores.nodeName == 'scores' 
                && node.nodeValue.possible && node.nodeValue.possible.nodeName == 'possible';
        }
        function levelCount(node) {
            if (isLevelNode(node)) {
                return node.nodeValue.scores.childNodes.length;
            } else {
                return null;
            }
        }
        function levelAverage(node) {
            if (isLevelNode(node)) {
                // Assume the scores node has children.
                var scores = node.nodeValue.scores.childNodes;
                var sum = 0;
                for (var i in scores) sum += scores[i].nodeValue;
                return sum / scores.length;
            } else {
                return null;
            }
        }
        function levelPossible(node) {
            if (isLevelNode(node)) {
                return node.nodeValue.possible.nodeValue;
            } else {
                return null;
            }
        }
        function levelFraction(node) {
            return levelAverage(node) / levelPossible(node);
        }
        function levelScores(node) {
            return node.nodeValue.scores.childNodes.map(function(n) n.nodeValue);
        }

        var showFraction = false; // label each partition with fraction of points obtained by student group
        var colorize = false; // show performance differences by shades of green
        var showHist = false; // insert histograms into cells

        var mathNode = subjectNode(itemData, 'Math',6,'John F Kennedy Middle School',2010);
        itemData.visitBefore(stowScores);

        function bins(node) {
            var bins0 = pv.histogram(levelScores(node).map(function(s) s+0.5)).frequency(false).bins(pv.range(0, levelPossible(node) + 2));
            //if (node.nodeName == '14') throw(bins0.map(function(b) b.y));
            return bins0;
        }
        function hLeft(bin, cell) {
            var node = cell.parent.data();
            var left = pv.Scale.linear(0, levelPossible(node) + 1).range(0, cell.parent.width())(bin.x);
            return left;
        }
        function hWidth(bin, cell) {
            var node = cell.parent.data();
            var width = pv.Scale.linear(0, levelPossible(node)).range(0, cell.parent.width())(bin.dx);
            return width;
        }
        function hHeight(bin, cell) {
            var node = cell.parent.data();
            var height = pv.Scale.linear(0, 1).range(0, cell.parent.height())(bin.y);
            return height;
        }

        var vis = new pv.Panel()
            .width(1000)
            .height(700);
        var layout = vis.add(pv.Layout.Partition.Fill)
            .nodes(mathNode.nodes())
            .order("descending")
            .orient("left")
            .size(function(n) levelPossible(n));
        var cell = layout.node.add(pv.Bar)
            .fillStyle(function(n) colorize ? ("rgb(0," + Number(levelFraction(n) * 255).toFixed(0) + ",0)") : 'lightgray')
            .strokeStyle("rgba(255,255,255,.5)")
            .lineWidth(1)
            .antialias(false)
        .anchor('right').add(pv.Label)
            .visible(function(d) showFraction)
            .textStyle('yellow')
            .text(function(n) (levelFraction(n) * 100).toFixed(0));
        var fractionCell = layout.node.add(pv.Panel)
            .fillStyle('transparent')
            .strokeStyle('transparent')
            ;
        var histCell = layout.node.add(pv.Panel)
            .fillStyle('transparent')
            .strokeStyle('transparent')
        .add(pv.Bar)
            .visible(function() showHist)
            .fillStyle("rgb(176, 196, 222, 0.2)")
            .data(function(n) bins(n))
            .bottom(0)
            .left(function(b) hLeft(b, histCell))
            .width(function(b) hWidth(b, histCell))
            .height(function(b) hHeight(b, histCell))
            ;
        layout.label.add(pv.Label);

        vis.render();
        function toggleColorize() {
            colorize = document.getElementsByName('colorizeBox')[0].checked;
            vis.render();
        }
        function toggleFraction() {
            showFraction = document.getElementsByName('showFractionBox')[0].checked;
            vis.render();
        }
        function toggleHist() {
            showHist = document.getElementsByName('showHistBox')[0].checked;
            vis.render();
        }
        </script>
        </div>
        <form>
            <table>
                <tr>
                    <td><input type=checkbox name=colorizeBox onclick="toggleColorize()">colorize</input></td>
                    <td><input type=checkbox name=showFractionBox onclick="toggleFraction()">percentage scores</input></td>
                    <td><input type=checkbox name=showHistBox onclick="toggleHist()">histograms</input></td>
                </tr>
            </table>
        </form>
    </body>
</html>
<!--
The 'level' variable identifies each level of the hierarchy.
After initial processing we know we're at a 'level' node if our first child is 'possible'.
Initial processing takes each student's item scores and accumulates them up the levels to subject level.
After initial processing each 'level' node has 
  a 'scores' child, always last, whose children are students and their values scores, and a
  'possible' child, always first, whose value is the number of points possible at that level.

root
  year
    month
      school
        grade
          subject
            possible.score
            strand1..................
            strand2..................
            scores                possible.score
              student1.score      standard1..................
              student2.score      standard2..................
              ...                 scores                  possible
                                    student1.score        type1             and so on to item level
                                    student2.score        type2
                                    ...                   student1.score
-->
